<?php
/**
 * 智能门禁设备TCP服务器
 * - 监听端口: 0.0.0.0:8778
 * - 协议格式: 4B标识'OFZL' + 1B版本 + 1B数据类型 + 1B命令 + 4B长度 + JSON消息体
 * - 支持设备登录验证、心跳保活、人员数据同步、识别记录上传等功能
 * - 与打卡积分系统集成，实现完整的签到功能
 *
 * 使用方法: php tools/device_tcp/server.php
 */

// 引入ThinkAdmin框架
require_once __DIR__ . '/../../vendor/autoload.php';

error_reporting(E_ALL);
ini_set('display_errors', 'on');
set_time_limit(0);

// 初始化ThinkAdmin应用
try {
    $app = new \think\App();
    $app->initialize();
    echo "ThinkAdmin framework initialized successfully\n";
} catch (\Throwable $e) {
    echo "Failed to initialize ThinkAdmin: " . $e->getMessage() . "\n";
    echo "Running in standalone mode without database features\n";
    $app = null;
}

$host = '0.0.0.0';
$port = 8778;
$marker = "OFZL"; // 0x4F 0x46 0x5A 0x4C
$version = 0x02;   // protocol version
$dtype = 0x01;     // 0x01 json
$logDir = __DIR__ . '/logs';
if (!is_dir($logDir)) @mkdir($logDir, 0777, true);

// 命令映射: 请求 -> 响应
$cmdMap = [
    0x01 => 0x02, // loginReq -> loginRsp
    0x05 => 0x06, // registerReq -> registerRsp
    0x07 => 0x08, // heartbeatReq -> heartbeatRsp
    0x09 => 0x0A, // getUserReq -> getUserRsp
    0x0B => 0x0C, // allUserReq -> allUserRsp
    0x0D => 0x0E, // snapshotFaceReq -> snapshotFaceRsp
];

// 命令名称映射
$cmdNames = [
    0x01 => 'loginReq',
    0x05 => 'registerReq',
    0x07 => 'heartbeatReq',
    0x09 => 'getUserReq',
    0x0B => 'allUserReq',
    0x0D => 'snapshotFaceReq',
];

$server = @stream_socket_server("tcp://{$host}:{$port}", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
if (!$server) {
    fwrite(STDERR, "Failed to bind {$host}:{$port} - {$errno} {$errstr}\n");
    exit(1);
}

logMsg("Server started on tcp://{$host}:{$port}");

while (true) {
    $conn = @stream_socket_accept($server, -1, $peer);
    if ($conn === false) {
        usleep(10000);
        continue;
    }
    stream_set_timeout($conn, 60);
    logMsg("Accepted connection from {$peer}");

    try {
        $buffer = '';
        while (true) {
            // read fixed 11-byte header
            $hdr = readExact($conn, 11);
            if ($hdr === '') { break; }
            if (strlen($hdr) < 11) { throw new \RuntimeException('header short read'); }

            $m = substr($hdr, 0, 4);
            $ver = ord($hdr[4] ?? "\0");
            $dt  = ord($hdr[5] ?? "\0");
            $cmd = ord($hdr[6] ?? "\0");
            $lenBytes = substr($hdr, 7, 4);
            $lenLittleEndian = unpack('Vlen', $lenBytes)['len']; // little-endian uint32
            $lenBigEndian = unpack('Nlen', $lenBytes)['len']; // big-endian uint32

            // 智能选择字节序：选择更合理的长度值
            if ($lenBigEndian <= 50 * 1024 * 1024 && $lenLittleEndian > 50 * 1024 * 1024) {
                // 大端序的值更合理
                $len = $lenBigEndian;
                $endianness = 'big-endian';
            } elseif ($lenLittleEndian <= 50 * 1024 * 1024 && $lenBigEndian > 50 * 1024 * 1024) {
                // 小端序的值更合理
                $len = $lenLittleEndian;
                $endianness = 'little-endian';
            } else {
                // 两个都合理或都不合理，默认使用小端序
                $len = $lenLittleEndian;
                $endianness = 'little-endian (default)';
            }

            logMsg("[DEBUG] Length detection: LE={$lenLittleEndian}, BE={$lenBigEndian}, chosen={$len} ({$endianness})");

            // 验证协议标识符
            if ($m !== $marker) {
                throw new \RuntimeException("invalid marker: expected '{$marker}', got '" . toHex($m) . "'");
            }

            // 验证版本号
            if ($ver !== $version) {
                logMsg("[WARN] version mismatch: expected {$version}, got {$ver}");
            }

            // 防止内存溢出：限制最大包体长度为50MB
            $maxBodySize = 50 * 1024 * 1024; // 50MB
            if ($len > $maxBodySize) {
                throw new \RuntimeException("body length too large: {$len} bytes (max: {$maxBodySize})");
            }

            $body = readExact($conn, $len);
            if ($len > 0 && strlen($body) < $len) { throw new \RuntimeException('body short read'); }

            // prepare log
            $headerHex = toHex($hdr);
            $bodyHex = toHex($body);
            $json = null; $parsed = null;
            if ($dt === 0x01 && $len > 0) {
                $json = $body;
                $parsed = json_decode($json, true);
            }

            $cmdName = $cmdNames[$cmd] ?? 'unknown';
            logMsg("<-- from {$peer} | cmd=0x" . strtoupper(str_pad(dechex($cmd),2,'0',STR_PAD_LEFT)) . " ({$cmdName}), len={$len}\n  header={$headerHex}\n  body={$bodyHex}\n  json=" . ($json ?? ''));

            // 处理业务逻辑
            $respArr = handleDeviceMessage($cmd, $parsed, $peer);
            $respJson = json_encode($respArr, JSON_UNESCAPED_UNICODE);
            $respCmd = $cmdMap[$cmd] ?? $cmd; // echo or map to rsp

            // 构建响应包（使用与请求相同的字节序）
            $respLenBytes = ($endianness === 'big-endian') ? pack('N', strlen($respJson)) : pack('V', strlen($respJson));
            $respHdr = $marker . chr($version) . chr($dtype) . chr($respCmd) . $respLenBytes;
            $respPkt = $respHdr . $respJson;
            fwrite($conn, $respPkt);

            $respCmdName = $cmdNames[$respCmd] ?? 'unknown';
            logMsg("--> to   {$peer} | cmd=0x" . strtoupper(str_pad(dechex($respCmd),2,'0',STR_PAD_LEFT)) . " ({$respCmdName}), len=" . strlen($respJson) . "\n  header=" . toHex($respHdr) . "\n  body=" . toHex($respJson) . "\n  json={$respJson}");
        }
    } catch (\Throwable $e) {
        logMsg("[ERROR] {$peer} {$e->getMessage()}");
    } finally {
        fclose($conn);
        logMsg("Closed {$peer}");
    }
}

function readExact($conn, int $len): string {
    if ($len < 0) {
        return '';
    }
    if ($len === 0) {
        return '';
    }

    $buf = '';
    $maxChunkSize = 8192; // 每次最多读取8KB
    $attempts = 0;
    $maxAttempts = 1000; // 最多尝试1000次，防止无限循环

    while (strlen($buf) < $len && $attempts < $maxAttempts) {
        $remaining = $len - strlen($buf);
        $chunkSize = min($remaining, $maxChunkSize);

        $chunk = fread($conn, $chunkSize);
        if ($chunk === '' || $chunk === false) {
            // connection closed or error
            break;
        }
        $buf .= $chunk;
        $attempts++;
    }
    return $buf;
}

function toHex(string $bin): string {
    return strtoupper(implode(' ', str_split(bin2hex($bin), 2)));
}

function logMsg(string $msg): void {
    $file = __DIR__ . '/logs/' . date('Y-m-d') . '.log';
    $line = '[' . date('Y-m-d H:i:s') . '] ' . $msg . "\n";
    file_put_contents($file, $line, FILE_APPEND);
}

/**
 * 处理设备消息
 * @param int $cmd 命令码
 * @param array|null $data 解析后的JSON数据
 * @param string $peer 客户端地址
 * @return array 响应数据
 */
function handleDeviceMessage(int $cmd, ?array $data, string $peer): array
{
    global $app;

    try {
        // 如果框架未初始化，返回简单响应
        if ($app === null) {
            logMsg("[WARN] Framework not initialized, returning simple response for cmd 0x" . dechex($cmd));
            return ['code' => 200, 'msg' => 'ok (standalone mode)'];
        }

        switch ($cmd) {
            case 0x01: // 登录请求
                return handleLoginRequest($data, $peer);
            case 0x05: // 注册请求
                return handleRegisterRequest($data, $peer);
            case 0x07: // 心跳请求
                return handleHeartbeatRequest($data, $peer);
            case 0x09: // 获取用户信息请求
                return handleGetUserRequest($data, $peer);
            case 0x0B: // 获取所有用户ID请求
                return handleGetAllUsersRequest($data, $peer);
            case 0x0D: // 识别记录上传
                return handleRecognitionRecord($data, $peer);
            default:
                logMsg("[WARN] Unknown command: 0x" . dechex($cmd) . " from {$peer}");
                return ['code' => 1000, 'msg' => '未知命令'];
        }
    } catch (\Throwable $e) {
        logMsg("[ERROR] Handle message error: " . $e->getMessage() . " from {$peer}");
        logMsg("[ERROR] Stack trace: " . $e->getTraceAsString());
        return ['code' => 500, 'msg' => '服务器内部错误'];
    }
}

/**
 * 处理登录请求
 * @param array|null $data
 * @param string $peer
 * @return array
 */
function handleLoginRequest(?array $data, string $peer): array
{
    try {
        if (!$data) {
            return ['code' => 400, 'msg' => '请求数据格式错误'];
        }

        // 提取登录信息
        $sn = strval($data['sn'] ?? '');
        $sign = strval($data['data']['sign'] ?? '');
        $timestamp = intval($data['data']['timestamp'] ?? 0);
        $dversion = strval($data['data']['dversion'] ?? '');
        $protocolVersion = strval($data['data']['protocolVersion'] ?? '');

        if (empty($sn) || empty($sign) || $timestamp <= 0) {
            return ['code' => 400, 'msg' => '登录参数不完整'];
        }

        logMsg("[LOGIN] Device {$sn} attempting login from {$peer}, version: {$dversion}, protocol: {$protocolVersion}");

        // 验证设备登录
        $result = \app\checkin\model\Device::verifyLogin($sn, $sign, $timestamp);

        if ($result['code'] === 200) {
            logMsg("[LOGIN] Device {$sn} login success from {$peer}");

            // 更新设备IP地址
            $ip = explode(':', $peer)[0];
            \app\checkin\model\Device::mk()->where(['sn' => $sn])->update([
                'ip_address' => $ip,
                'firmware_version' => $dversion,
                'protocol_version' => $protocolVersion,
            ]);

            return [
                'code' => 200,
                'msg' => '登录成功',
                'data' => [
                    'serverTime' => time(),
                    'config' => $result['device']['config'] ?? []
                ]
            ];
        } else {
            logMsg("[LOGIN] Device {$sn} login failed from {$peer}: " . $result['msg']);
            return $result;
        }
    } catch (\Throwable $e) {
        logMsg("[LOGIN] Exception: " . $e->getMessage());
        return ['code' => 500, 'msg' => '登录处理异常'];
    }
}

/**
 * 处理注册请求
 * @param array|null $data
 * @param string $peer
 * @return array
 */
function handleRegisterRequest(?array $data, string $peer): array
{
    if (!$data) {
        return ['code' => 400, 'msg' => '请求数据格式错误'];
    }

    $sn = strval($data['sn'] ?? '');
    if (empty($sn)) {
        return ['code' => 400, 'msg' => '设备序列号不能为空'];
    }

    logMsg("[REGISTER] Device {$sn} attempting register from {$peer}");

    // 检查设备是否已存在
    $device = \app\checkin\model\Device::mk()->where(['sn' => $sn])->findOrEmpty();
    if (!$device->isEmpty()) {
        return ['code' => 1004, 'msg' => '设备已注册'];
    }

    // 生成设备密码
    $passwd = \app\checkin\model\Device::generatePassword($sn);
    $ip = explode(':', $peer)[0];

    // 创建设备记录
    $deviceData = [
        'store_id' => 1, // 默认分配到第一个门店，后续可以在后台修改
        'sn' => $sn,
        'name' => "设备_{$sn}",
        'passwd' => $passwd,
        'ip_address' => $ip,
        'status' => 1,
        'online_status' => 1,
    ];

    try {
        \app\checkin\model\Device::mk()->save($deviceData);
        logMsg("[REGISTER] Device {$sn} registered successfully from {$peer}");

        return [
            'code' => 200,
            'msg' => '注册成功',
            'data' => [
                'passwd' => $passwd,
                'serverTime' => time()
            ]
        ];
    } catch (\Throwable $e) {
        logMsg("[REGISTER] Device {$sn} register failed: " . $e->getMessage());
        return ['code' => 500, 'msg' => '注册失败'];
    }
}

/**
 * 处理心跳请求
 * @param array|null $data
 * @param string $peer
 * @return array
 */
function handleHeartbeatRequest(?array $data, string $peer): array
{
    if (!$data) {
        return ['code' => 400, 'msg' => '请求数据格式错误'];
    }

    $sn = strval($data['sn'] ?? '');
    if (empty($sn)) {
        return ['code' => 400, 'msg' => '设备序列号不能为空'];
    }

    // 更新设备心跳时间
    $success = \app\checkin\model\Device::updateHeartbeat($sn);

    if ($success) {
        return [
            'code' => 200,
            'msg' => '心跳成功',
            'data' => [
                'serverTime' => time()
            ]
        ];
    } else {
        return ['code' => 1002, 'msg' => '设备不存在'];
    }
}

/**
 * 处理获取用户信息请求
 * @param array|null $data
 * @param string $peer
 * @return array
 */
function handleGetUserRequest(?array $data, string $peer): array
{
    // TODO: 实现获取单个用户信息逻辑
    return [
        'code' => 200,
        'msg' => '获取用户信息成功',
        'data' => []
    ];
}

/**
 * 处理获取所有用户ID请求
 * @param array|null $data
 * @param string $peer
 * @return array
 */
function handleGetAllUsersRequest(?array $data, string $peer): array
{
    // TODO: 实现获取所有用户ID列表逻辑
    return [
        'code' => 200,
        'msg' => '获取用户列表成功',
        'data' => [
            'userIds' => [],
            'updateDate' => date('Y-m-d H:i:s')
        ]
    ];
}

/**
 * 处理识别记录上传（核心签到功能）
 * @param array|null $data
 * @param string $peer
 * @return array
 */
function handleRecognitionRecord(?array $data, string $peer): array
{
    if (!$data) {
        return ['code' => 400, 'msg' => '请求数据格式错误'];
    }

    $sn = strval($data['sn'] ?? '');
    if (empty($sn)) {
        return ['code' => 400, 'msg' => '设备序列号不能为空'];
    }

    logMsg("[RECOGNITION] Device {$sn} uploaded recognition record from {$peer}");
    logMsg("[RECOGNITION] Data: " . json_encode($data, JSON_UNESCAPED_UNICODE));

    // TODO: 实现识别记录处理和签到逻辑
    // 1. 验证设备权限
    // 2. 解析识别数据（人脸、卡号、体温等）
    // 3. 查找对应会员
    // 4. 创建打卡记录
    // 5. 计算积分奖励

    return [
        'code' => 200,
        'msg' => '记录上传成功',
        'data' => [
            'recordId' => time(), // 临时使用时间戳作为记录ID
            'points' => 0 // 获得的积分
        ]
    ];
}

